package com.tlgen.utils;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.tlgen.domain.ColumnVO;
import com.tlgen.enums.TypeEnum;
import com.tlgen.factory.GeneratorFactory;
import freemarker.cache.ClassTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;

@Component
public class GenerateUtils {

    public static Map<String, Function<String, String>> typeMap = new HashMap<>();

    static {
        typeMap.put(TypeEnum.STRING.getValue(), (c) -> GeneratorFactory.String());
        typeMap.put(TypeEnum.INTEGER.getValue(), (c) -> GeneratorFactory.Integer());
        typeMap.put(TypeEnum.JSONOBJECT.getValue(), (c) -> GeneratorFactory.JSONObject());
        typeMap.put(TypeEnum.JSONARRAY.getValue(), (c) -> GeneratorFactory.JSONArray());
    }

    static Configuration cfg = null;

    {
        cfg = new Configuration(Configuration.VERSION_2_3_30);
        cfg.setTemplateLoader(new ClassTemplateLoader(GenerateUtils.class, "/templates"));
        cfg.setDefaultEncoding("UTF-8");
    }

    /**
     * 判断生成对象或数组
     *
     * @param classType
     * @param con1
     * @param con2
     */
    public static void generateClass(String classType, Consumer<String> con1, Consumer<String> con2) {
        if (Objects.equals(TypeEnum.JSONOBJECT.getValue(), classType)) {
            con1.accept(classType);
        } else if (Objects.equals(TypeEnum.JSONARRAY.getValue(), classType)) {
            con2.accept(classType);
        }
    }

    /**
     * 遍历子项生成
     *
     * @param jsonObject
     */
    public static void iteratorGenerator(JSONObject jsonObject, String path, String classExtendName, String packageName) {
        for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {

            String classType = StringUtils.subLastName(String.valueOf(entry.getValue().getClass()));

            if (null != entry.getValue()) {
                generateClass(classType, (c) -> {
                    JSONObject objectValue = JSONObject.parseObject(JSONObject.toJSONString(entry.getValue()));
                    ColumnVO v = new ColumnVO();
                    v.setPackageName(packageName);
                    v.setClassExtendName(classExtendName);
                    v.setClassName(entry.getKey().substring(0, 1).toUpperCase().concat(entry.getKey().substring(1)).concat(classExtendName));
                    v.setColumnList(getColumnList(objectValue));
                    // 生成
                    generateCode(v, path);
                }, (c) -> {
                    JSONArray arrayValue = JSONArray.parseArray(JSONArray.toJSONString(entry.getValue()));
                    ColumnVO v = new ColumnVO();
                    v.setPackageName(packageName);
                    v.setClassExtendName(classExtendName);
                    v.setClassName(entry.getKey()
                            .substring(0, 1)
                            .toUpperCase()
                            .concat(entry.getKey().substring(1, entry.getKey().indexOf("L"))).concat(classExtendName));
                    // 获取合适 (当一个数组中包含多个对象, 取出包含属性个数最多的对象进行生成) 的 JSONObject 对象
                    JSONObject object = getMaxLengthJSONObject(arrayValue);
                    v.setColumnList(getColumnList(object));
                    // 生成
                    generateCode(v, path);

                    // 如果这个对象依然有值, 则递归继续寻找并生成策略
                    if (null != object) {
                        iteratorGenerator(object, path, classExtendName, packageName);
                    }
                });
            }
        }
    }

    /**
     * 根据对象获取属性以及类型
     *
     * @param jsonObject
     * @return
     */
    public static List<ColumnVO> getColumnList(JSONObject jsonObject) {
        List<ColumnVO> voList = new ArrayList<>();
        for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
            String classType = StringUtils.subLastName(String.valueOf(entry.getValue().getClass()));
            ColumnVO c = new ColumnVO();
            c.setColumnName(entry.getKey());
            c.setColumnType(typeMap.get(classType).apply(null));
            c.setColumnList(new ArrayList<>());
            voList.add(c);
        }
        return voList;
    }


    /**
     * 加载指定模板
     *
     * @param vo
     * @param path
     */
    public static void generateCode(ColumnVO vo, String path) {
        try {
            Template modelTemplate = cfg.getTemplate("vo.java.ftl");
            generateCodeTemplate(modelTemplate, vo, path);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 生成模板处理
     *
     * @param template
     * @param vo
     * @param path
     * @throws IOException
     * @throws TemplateException
     */
    public static void generateCodeTemplate(Template template, ColumnVO vo, String path) throws IOException, TemplateException {
        String fileName = path + vo.getClassName().substring(0, 1).toUpperCase() + vo.getClassName().substring(1) + ".java";
        FileOutputStream fos = new FileOutputStream(fileName);
        OutputStreamWriter out = new OutputStreamWriter(fos);
        template.process(vo, out);
        fos.close();
        out.close();
    }


    public static JSONObject getMaxLengthObject(Object json, Function<Object, JSONObject> fu, Function<Object, JSONObject> fun) {
        if (json instanceof ArrayList || json instanceof JSONArray) {  // ArrayList || JSONArray
            return fu.apply(json);
        }
        return fun.apply(json);  // LinkedHashMap
    }

    /**
     * 获取 JSONObject
     * @param json
     * @return
     */
    public static JSONObject getMaxLengthJSONObject(Object json) {
        return getMaxLengthObject(json, (j) -> {
            JSONArray array = JSONArray.parseArray(JSONArray.toJSONString(json));
            TreeMap<Integer, JSONObject> treeMap = new TreeMap<>();
            for (int i = 0; i < array.size(); i++) {
                int size = array.getJSONObject(i).entrySet().size();
                treeMap.put(size, array.getJSONObject(i));
            }
            return treeMap.get(treeMap.lastKey());
        }, (j) -> {
            return JSONObject.parseObject(JSONObject.toJSONString(json));
        });

    }


}